#pragma once
#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
#include <queue>
#include <atomic>
#include <functional>
#include <condition_variable>
#include <map>
#include <future>
using namespace std;

class ThreadPool {
public:
    ThreadPool(int min=4, int max=thread::hardware_concurrency());
    ~ThreadPool();
    template<typename F, typename... Args>
    auto addTask(F&& f, Args&&... args) -> std::future<typename std::result_of<F(Args...)>::type> {
        using returnType = typename std::result_of<F(Args...)>::type;
        auto task = std::make_shared<std::packaged_task<returnType()>>(
            std::bind(std::forward<F>(f), std::forward<Args>(args)...)
        );
        std::future<returnType> res = task->get_future();
        {
            std::unique_lock<mutex> lock(m_queueMutex);
            m_tasks.emplace([task]() {
                (*task);
            });
            m_condition.notify_one();
            return res;
        }
    }
private:
    void manager();
    void worker();
private:
    thread* m_manager;
    map<thread::id, thread> m_workers;
    vector<thread::id> m_ids;
    int m_minThreads;
    int m_maxThreads;
    atomic<bool> m_stop;
    atomic<int> m_curThreads;   // current thread's num
    atomic<int> m_idleThreads;  // rest threads
    atomic<int> m_exitNumber;
    queue<function<void()>> m_tasks;
    mutex m_idsMutex;
    mutex m_queueMutex;
    condition_variable m_condition;
};


ThreadPool::ThreadPool(int min, int max):
    m_maxThreads(max), m_minThreads(min), m_stop(false), m_exitNumber(0) {
    
    m_idleThreads = m_curThreads = min;
    std::cout << "curent thread's num: " << m_curThreads << endl;
    m_manager = new thread(&ThreadPool::manager, this);
    for (int i = 0; i < m_curThreads; ++i) {
        thread t(&ThreadPool::worker, this);
        m_workers.insert(std::make_pair(t.get_id(), std::move(t)));
    }
}

// deconstructor
ThreadPool::~ThreadPool() {
    m_stop = true; // set stop
    m_condition.notify_all();
    for (auto& it : m_workers) {
        thread& t = it.second;
        if (t.joinable()) {
            std::cout << "---thread[" << t.get_id() << "] will exit...\n";
            t.join();
        }
    }
    if (m_manager->joinable()) {
        m_manager->join();
    }
    delete m_manager;
}

void ThreadPool::manager() {
    while (!m_stop.load()) {
        // wait 2 seconds, check work threads.
        this_thread::sleep_for(std::chrono::seconds(2));
        int idle = m_idleThreads.load();
        int current = m_curThreads.load();

        // if rest threads num > all threads num/2, destory 2
        if (idle > current/2 && current > m_minThreads) {
            m_exitNumber.store(2);
            m_condition.notify_all();
            unique_lock<mutex> lock(m_idsMutex); // add lock
            for (const auto& id : m_ids) {
                auto it = m_workers.find(id);
                if (it != m_workers.end()) {
                    std::cout << "---thread[" << (*it).first << "] destoryed...\n";
                    (*it).second.join();
                    m_workers.erase(it);
                }
            }
            m_ids.clear();
        } else if (idle == 0 && current < m_maxThreads) {
            thread t(&ThreadPool::worker, this);
            std::cout << "---added a new thread, id: " << t.get_id() << std::endl;
            m_workers.insert(make_pair(t.get_id(), std::move(t)));
            m_curThreads++;
            m_idleThreads++;
        }
    }    
}

// worker threads
void ThreadPool::worker() {
    while (!m_stop.load()) {
        // get data from task queue, execute data.
        function<void()> task = nullptr;
        {
            unique_lock<mutex> locker(m_queueMutex);
            // execute tasks
            while (!m_stop && m_tasks.empty()) {
                m_condition.wait(locker);
                if (m_exitNumber.load() > 0) {
                    std::cout << "--thread task end, id:" << this_thread::get_id() << std::endl;
                    m_exitNumber--;
                    m_curThreads--;
                    unique_lock<mutex> lock(m_idsMutex);
                    m_ids.emplace_back(this_thread::get_id());
                    return;
                }
            }
            if (!m_tasks.empty()) {
                std::cout << "get a task...\n";
                task = std::move(m_tasks.front());
                m_tasks.pop();
            }
        }
        if (task) {
            m_idleThreads--;
            task();
            m_idleThreads++;
        }
    }
}
